package org.xx.armory.config.app;

import org.apache.commons.lang3.time.DateUtils;
import org.xx.armory.commons.ValueEnum;

import java.math.BigDecimal;
import java.text.ParseException;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import static org.apache.commons.lang3.math.NumberUtils.toByte;
import static org.apache.commons.lang3.math.NumberUtils.toDouble;
import static org.apache.commons.lang3.math.NumberUtils.toInt;
import static org.apache.commons.lang3.math.NumberUtils.toLong;
import static org.apache.commons.lang3.math.NumberUtils.toShort;
import static org.xx.armory.commons.Converter.toValueEnum;
import static org.xx.armory.commons.Validators.notBlank;
import static org.xx.armory.commons.Validators.notNull;

/**
 * App配置节。
 */
public final class AppSection {
    private final Map<String, CategorySection> categories;

    /**
     * 构造App配置节的实例，不包含任何Category配置节。
     */
    public AppSection() {
        this.categories = new HashMap<>();
    }

    /**
     * 添加一个Category配置节。
     *
     * @param item
     *         待添加的Category配置节。
     * @throws IllegalArgumentException
     *         如果参数{@code item}是{@code null}。
     */
    public final void addCategory(
            CategorySection item
    ) {
        this.categories.put(item.getName(), item);
    }

    /**
     * 获取指定名字的Category配置节。
     *
     * @param name
     *         配置节的名字。
     * @return 指定名字的配置节，如果不存在则返回{@code null}。
     */
    public final CategorySection getCategory(
            String name
    ) {
        final CategorySection categorySection = this.categories.get(name);
        if (categorySection == null) {
            return null;
        } else if (!categorySection.isEnabled()) {
            return null;
        } else {
            return categorySection;
        }
    }

    /**
     * 获取指定名字的配置项的值。
     *
     * @param fullName
     *         配置项的全名。
     * @return 配置项的值，如果指定的Category不存在或者属性不存在则返回空字符串。
     * @throws IllegalArgumentException
     *         如果参数{@code fullName}是{@code null}或者只包含空白字符，或者未指定配置项名。
     */
    public final String getString(
            String fullName
    ) {
        fullName = notBlank(fullName, "fullName").trim();
        int indexOfDot = fullName.indexOf('.');
        if (indexOfDot == -1 || indexOfDot >= fullName.length() - 1) {
            throw new IllegalArgumentException("name of property cannot be empty");
        }

        final String categoryName = fullName.substring(0, indexOfDot).trim();
        final String propertyName = fullName.substring(indexOfDot + 1).trim();

        final CategorySection categorySection = getCategory(categoryName);
        if (categorySection != null) {
            return categorySection.getProperty(propertyName);
        } else {
            return "";
        }
    }

    /**
     * 获取指定名字的配置项的值。
     *
     * @param fullName
     *         配置项的全名。
     * @param defaultValue
     *         配置项的默认值。
     * @return 配置项的值，如果指定的Category不存在或者属性不存在则返回参数{@code defaultValue}的值。
     * @throws IllegalArgumentException
     *         如果参数{@code fullName}是{@code null}或者只包含空白字符，或者未指定配置项名。
     */
    public final int getInteger(
            String fullName,
            int defaultValue
    ) {
        return toInt(getString(fullName), defaultValue);
    }

    /**
     * 获取指定名字的配置项的值。
     *
     * @param fullName
     *         配置项的全名。
     * @param defaultValue
     *         配置项的默认值。
     * @return 配置项的值，如果指定的Category不存在或者属性不存在则返回参数{@code defaultValue}的值。
     * @throws IllegalArgumentException
     *         如果参数{@code fullName}是{@code null}或者只包含空白字符，或者未指定配置项名。
     */
    public final long getLong(
            String fullName,
            long defaultValue
    ) {
        return toLong(getString(fullName), defaultValue);
    }

    /**
     * 获取指定名字的配置项的值。
     *
     * @param fullName
     *         配置项的全名。
     * @param defaultValue
     *         配置项的默认值。
     * @return 配置项的值，如果指定的Category不存在或者属性不存在则返回参数{@code defaultValue}的值。
     * @throws IllegalArgumentException
     *         如果参数{@code fullName}是{@code null}或者只包含空白字符，或者未指定配置项名。
     */
    public final byte getByte(
            String fullName,
            byte defaultValue
    ) {
        return toByte(getString(fullName), defaultValue);
    }

    /**
     * 获取指定名字的配置项的值。
     *
     * @param fullName
     *         配置项的全名。
     * @param defaultValue
     *         配置项的默认值。
     * @return 配置项的值，如果指定的Category不存在或者属性不存在则返回参数{@code defaultValue}的值。
     * @throws IllegalArgumentException
     *         如果参数{@code fullName}是{@code null}或者只包含空白字符，或者未指定配置项名。
     */
    public final short getShort(
            String fullName,
            short defaultValue
    ) {
        return toShort(getString(fullName), defaultValue);
    }

    /**
     * 获取指定名字的配置项的值。
     *
     * @param fullName
     *         配置项的全名。
     * @param defaultValue
     *         配置项的默认值。
     * @return 配置项的值，如果指定的Category不存在或者属性不存在则返回参数{@code defaultValue}的值。
     * @throws IllegalArgumentException
     *         如果参数{@code fullName}是{@code null}或者只包含空白字符，或者未指定配置项名。
     */
    public final BigDecimal getDecimal(
            String fullName,
            BigDecimal defaultValue
    ) {
        final String s = getString(fullName).trim();
        if (s.isEmpty()) {
            return defaultValue;
        }

        try {
            return new BigDecimal(s);
        } catch (NumberFormatException ex) {
            return defaultValue;
        }
    }

    /**
     * 获取指定名字的配置项的值。
     *
     * @param fullName
     *         配置项的全名。
     * @param defaultValue
     *         配置项的默认值。
     * @return 配置项的值，如果指定的Category不存在或者属性不存在则返回参数{@code defaultValue}的值。
     * @throws IllegalArgumentException
     *         如果参数{@code fullName}是{@code null}或者只包含空白字符，或者未指定配置项名。
     */
    public final byte[] getBytes(
            String fullName,
            byte[] defaultValue
    ) {
        final String s = getString(fullName).trim();
        if (s.isEmpty()) {
            return defaultValue;
        }

        try {
            return Base64.getDecoder().decode(s);
        } catch (IllegalArgumentException ex) {
            return defaultValue;
        }
    }

    /**
     * 获取指定名字的配置项的值。
     *
     * @param fullName
     *         配置项的全名。
     * @param defaultValue
     *         配置项的默认值。
     * @return 配置项的值，如果指定的Category不存在或者属性不存在则返回参数{@code defaultValue}的值。
     * @throws IllegalArgumentException
     *         如果参数{@code fullName}是{@code null}或者只包含空白字符，或者未指定配置项名。
     */
    public final double getDouble(
            String fullName,
            double defaultValue
    ) {
        return toDouble(getString(fullName), defaultValue);
    }

    /**
     * 获取指定名字的配置项的值。
     *
     * @param fullName
     *         配置项的全名。
     * @param defaultValue
     *         配置项的默认值。
     * @return 配置项的值，如果指定的Category不存在或者属性不存在则返回参数{@code defaultValue}的值。
     * @throws IllegalArgumentException
     *         如果参数{@code fullName}是{@code null}或者只包含空白字符，或者未指定配置项名。
     */
    public final Date getDate(
            String fullName,
            Date defaultValue
    ) {
        final String s = getString(fullName);
        if (s.isEmpty()) {
            return defaultValue;
        }

        try {
            return DateUtils.parseDate(s, "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss Z");
        } catch (ParseException ex) {
            return defaultValue;
        }
    }

    /**
     * 获取指定名字的配置项的值。
     *
     * @param <T>
     *         配置项的值的类型，必须是枚举，并且实现了{@link ValueEnum}接口。
     * @param clazz
     *         配置项的值的类型。
     * @param fullName
     *         配置项的全名。
     * @param defaultValue
     *         配置项的默认值。
     * @return 配置项的值，如果指定的Category不存在或者属性不存在则返回参数{@code defaultValue}的值。
     * @throws IllegalArgumentException
     *         如果参数{@code clazz}是{@code null}或者参数{@code fullName}是{@code null}或者只包含空白字符，或者未指定配置项名。
     */
    public final <T extends Enum<T> & ValueEnum> T getValueEnum(
            Class<T> clazz,
            String fullName,
            T defaultValue
    ) {
        notNull(clazz, "clazz");

        final String s = getString(fullName);
        if (s.isEmpty()) {
            return defaultValue;
        }

        try {
            final T ret = toValueEnum(clazz, Integer.parseInt(s));
            return ret != null ? ret : defaultValue;
        } catch (NumberFormatException ex) {
            return defaultValue;
        }
    }
}
